<?php

namespace AmazonAdvertisingApi;

use AmazonAdvertisingApi\Contract\AuthorizationSignerContract;
use AmazonAdvertisingApi\Contract\RequestSignerContract;
use DateInterval;
use DateTime;
use DateTimeZone;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;
use RuntimeException;

class Authentication implements RequestSignerContract
{
    private $lwaClientId;
    private $lwaClientSecret;
    private $lwaRefreshToken = null;
    private $lwaAuthUrl      = null;
    private $endpoint;

    private $scope;

    //生成新的访问令牌时调用的回调函数
    private $onUpdateCreds;

    /** @var \GuzzleHttp\ClientInterface */
    private $client = null;

    /**
     * 授权凭证信息
     * @var null
     */
    private $Credentials = null;

    /**
     * Authentication constructor.
     * @param array $configurationOptions
     * @throws RuntimeException
     */
    public function __construct(array $configurationOptions)
    {
        $this->client = $configurationOptions['authenticationClient'] ?? new Client();

        $this->lwaAuthUrl = $configurationOptions['lwaAuthUrl'] ?? "https://api.amazon.com/auth/o2/token";
        $this->lwaRefreshToken = $configurationOptions['lwaRefreshToken'] ?? null;
        $this->onUpdateCreds = $configurationOptions['onUpdateCredentials'] ?? null;
        $this->lwaClientId = $configurationOptions['lwaClientId'];
        $this->lwaClientSecret = $configurationOptions['lwaClientSecret'];
        $this->endpoint = $configurationOptions['endpoint'];
        $this->scope =$configurationOptions['scope']??null;

        $accessToken = $configurationOptions['accessToken'] ?? null;
        $accessTokenExpiration = $configurationOptions['accessTokenExpiration'] ?? null;
        if ($accessToken !== null && $accessTokenExpiration !== null) {
            $this->Credentials= new Credentials($accessToken, $accessTokenExpiration);
        }
    }

    /**
     * @throws \GuzzleHttp\Exception\GuzzleException|\RuntimeException
     * @return array
     */
    public function requestLWAToken(): array
    {
        if ($this->lwaRefreshToken === null) {
            throw new RuntimeException('lwaRefreshToken must be specified when calling API operations');
        }
        $jsonData = [
            "grant_type" => "refresh_token",//advertising::campaign_management
            "client_id" => $this->lwaClientId,
            "client_secret" => $this->lwaClientSecret,
            "refresh_token"=>$this->lwaRefreshToken
        ];
        $lwaTokenRequestHeaders = [
            'Content-Type' => 'application/json',
        ];
        $lwaTokenRequestBody = \GuzzleHttp\Utils::jsonEncode($jsonData);
        $lwaTokenRequest = new Psr7\Request('POST', $this->lwaAuthUrl, $lwaTokenRequestHeaders, $lwaTokenRequestBody);
        $res = $this->client->send($lwaTokenRequest);
        $body = json_decode($res->getBody(), true);
        $accessToken = $body["access_token"];
        $expirationDate = new DateTime("now", new DateTimeZone("UTC"));
        $expirationDate->add(new DateInterval("PT" . strval($body["expires_in"]) . "S"));
        return [$accessToken, $expirationDate->getTimestamp()];
    }

    /**
     * Signs the given request using Amazon Signature V4.
     *
     * @param \Psr\Http\Message\RequestInterface $request The request to sign
     * @param ?string $scope If the request is to a grantless operation endpoint, the scope for the grantless token
     * @param ?string $restrictedPath The absolute (generic) path for the endpoint that the request is using if it's an endpoint that requires
     *      a restricted data token
     * @return \Psr\Http\Message\RequestInterface The signed request
     */
    public function signRequest(
        RequestInterface $request
    ): RequestInterface {
        $relevantCreds = $this->getCredentials();
        $signedRequest=$request->withHeader('Authorization', "Bearer {$relevantCreds->getSecurityToken()}")
            ->withHeader('Amazon-Advertising-API-ClientId', $this->lwaClientId)
            ->withHeader('Accept', "application/json");
        if($this->scope){
            $signedRequest= $signedRequest->withHeader('Amazon-Advertising-API-Scope', $this->scope);
        }
        return $signedRequest;
    }

    /**
     * Get credentials for standard API operations.
     *
     * @return \AmazonAdvertisingApi\Credentials A set of access credentials for making calls to the SP API
     */
    public function getCredentials(): Credentials
    {
        if ($this->needNewCredentials($this->Credentials)) {
            $this->newToken();
        }
        return $this->Credentials;
    }

    /**
     * Get LWA client ID.
     *
     * @return string
     */
    public function getLwaClientId(): ?string
    {
        return $this->lwaClientId;
    }

    /**
     * Set LWA client ID.
     *
     * @param string $lwaClientId
     * @return void
     */
    public function setLwaClientId(string $lwaClientId): void
    {
        $this->lwaClientId = $lwaClientId;
    }
    public function getScope():?string
    {
        return $this->scope;
    }

    public function setScope(string $scope):void
    {
        $this->scope=$scope;

    }

    /**
     * Get LWA client secret.
     *
     * @return string
     */
    public function getLwaClientSecret(): ?string
    {
        return $this->lwaClientSecret;
    }

    /**
     * Set LWA client secret.
     *
     * @param string $lwaClientSecret
     * @return void
     */
    public function setLwaClientSecret(string $lwaClientSecret): void
    {
        $this->lwaClientSecret = $lwaClientSecret;
    }

    /**
     * Get LWA refresh token.
     *
     * @return string|null
     */
    public function getLwaRefreshToken(): ?string
    {
        return $this->lwaRefreshToken;
    }

    /**
     * Set LWA refresh token.
     *
     * @param string|null $lwaRefreshToken
     * @return void
     */
    public function setLwaRefreshToken(?string $lwaRefreshToken = null): void
    {
        $this->lwaRefreshToken = $lwaRefreshToken;
    }
    /**
     * Get current SP API endpoint.
     *
     * @return array
     */
    public function getEndpoint(): array
    {
        return $this->endpoint;
    }

    /**
     * Set SP API endpoint. $endpoint should be one of the constants from Endpoint.php.
     *
     * @param string $endpoint
     * @return void
     */
    public function setEndpoint(string $endpoint): void
    {
        $this->endpoint = $endpoint;
    }

    /**
     * Check if the given credentials need to be created/renewed.
     *
     * @param ?\AmazonAdvertisingApi\Credentials $creds The credentials to check
     * @return bool True if the credentials need to be updated, false otherwise
     */
    private function needNewCredentials(?Credentials $creds = null): bool
    {
        return $creds === null || $creds->getSecurityToken() === null || $creds->expiresSoon();
    }

    private function newToken(): void
    {
        [$accessToken, $expirationTimestamp] = $this->requestLWAToken();
        $this->Credentials=new Credentials($accessToken, $expirationTimestamp);
        if ($this->onUpdateCreds !== null) {
            call_user_func($this->onUpdateCreds, $this->Credentials);
        }
    }
}
